package com.tencent.sr.iris.baisc.saas.export.sdk;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.google.common.base.Preconditions;
import com.tencent.sr.rmall.material.interfaces.enums.ETaskBusinessType;
import com.tencent.sr.rmall.material.interfaces.enums.ETaskStatus;
import com.tencent.sr.rmall.material.interfaces.response.TaskRecordResponse;
import com.tencent.sr.rmall.material.interfaces.vo.PaginationData;
import com.tencent.sr.rmall.springbootstarter.config.GlobalApplicationConfig;
import java.io.File;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.StringJoiner;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;


/**
 * 导出任务基础类
 *
 * @author xiaojun
 */

@Slf4j
public abstract class BaseExportTask<R, T> implements SimpleJob {

    @Autowired
    private CosDependency cosDependency;
    @Autowired
    private TaskDependency taskDependency;

    /**
     * 实现在导出数据后动态判断导出类型
     */
    @Getter
    private final ThreadLocal<Class<? extends T>> exportClass = new ThreadLocal<>();

    /**
     * 任务最大处理时间，超过改时间会被置为失败(毫秒)
     */
    private final long MAX_EXPIRED_TIME = 7200000L;

    /**
     * 导出任务需要实现的获取数据方法
     *
     * @param scrollRequest
     * @return
     */
    public abstract ScrollResponse<T> get(ScrollRequest<R> scrollRequest);

    /**
     * 导出任务需要实现的导出类型方法，导出类型找中台加枚举
     *
     * @return
     */
    public abstract String exportType();


    /**
     * 任务执行入口
     * @param shardingContext
     */
    @Override
    @Trace(operationName = "BaseExportTask")
    public void execute(ShardingContext shardingContext) {
        if (!GlobalApplicationConfig.IS_START_SUCCESS) {
            log.info("task not already, return; exportType:{}", exportType());
            return;
        }

        ETaskBusinessType businessType = ETaskBusinessType.valueOf(this.exportType());
        this.cancelExecutingTimeoutTask(businessType);

        Long taskId = null;
        TaskRecordResponse task;
        try {
            PaginationData<TaskRecordResponse> pageResult = this.taskDependency.getTask(ETaskStatus.WAIT, businessType);
            if (pageResult.getTotalCount() == 0L) {
                log.info("no businessType {} task found, abort", businessType.name());
                return;
            }

            task = pageResult.getPageList().get(0);
            taskId = Long.parseLong(task.getTaskId());
            this.taskDependency.updateTaskStatus(taskId, ETaskStatus.HANDLING, null);
            log.info("task handling, type={}, taskId={}", businessType.name(), taskId);
            TaskContextHolder.set(task);
            String fileName = this.append(task);
            this.uploadFile(fileName, businessType, taskId);
        } catch (Exception var15) {
            log.error("export taskId={} error occur", taskId, var15);
            this.exportFail(taskId);
        } finally {
            exportClass.remove();
            TaskContextHolder.clear();
        }
    }

    public static Type[] getClassTypes(Object o) {
        return ((ParameterizedType) o.getClass().getGenericSuperclass()).getActualTypeArguments();
    }

    public String append(TaskRecordResponse task) {
        Integer businessType = ETaskBusinessType.valueOf(this.exportType()).getStatus();
        Preconditions.checkArgument(businessType.equals(task.getBusinessType()),
                "taskId" + task.getTaskId() + "exportType error");
        Type[] types = getClassTypes(this);
        String taskParamJson = task.getRequestParam();
        Object searchRequest = null;
        ScrollRequest scrollRequest = new ScrollRequest();
        if (!StringUtils.isEmpty(taskParamJson)) {
            Class queryClass = (Class) types[0];
            searchRequest = JSON.parseObject(taskParamJson, queryClass);
            scrollRequest.setQueryCriteria(searchRequest);
        }

        scrollRequest.setPageNum(1);
        scrollRequest.setPageSize(200);
        String fileName = task.getReportFormName();
        String sheetName = ETaskBusinessType.valueOf(exportType()).getDesc();
        ExcelWriter excelWriter = null;
        WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
        ScrollResponse scrollResponse;
        do {
            scrollResponse = this.get(scrollRequest);
            if (excelWriter == null) {
                Class<? extends T> aClass = exportClass.get();
                if (aClass == null) {
                    Class clazz = (Class) types[1];
                    excelWriter = EasyExcel.write(fileName, clazz).build();
                } else {
                    excelWriter = EasyExcel.write(fileName, aClass).build();
                }
            }
            List dataList = scrollResponse.getDataList();
            excelWriter = write(excelWriter, writeSheet, dataList);
            scrollRequest.setPageNum(scrollRequest.getPageNum() + 1);
            scrollRequest.setScrollId(scrollResponse.getScrollId());
        } while (scrollResponse != null && !CollectionUtils.isEmpty(scrollResponse.getDataList()));

        excelWriter.finish();
        return fileName;
    }

    public static ExcelWriter write(ExcelWriter writer, WriteSheet writeSheet, List list) {
        return writer.write(list, writeSheet);
    }

    private void cancelExecutingTimeoutTask(ETaskBusinessType businessType) {
        PaginationData<TaskRecordResponse> pageResult = this.taskDependency.getTask(ETaskStatus.HANDLING, businessType);
        if (pageResult.getTotalCount() > 0L) {
            long time =
                    System.currentTimeMillis() - (pageResult.getPageList().get(0)).getCreateTime();

            if (time > MAX_EXPIRED_TIME) {
                log.info("processing upload task found but wait too long");
                this.taskDependency.updateTaskStatus(
                        Long.parseLong((pageResult.getPageList().get(0)).getTaskId()),
                        ETaskStatus.HANDLE_FAIL, null);
            } else {
                log.info("processing upload task found, abort");
            }
        }

    }

    public void uploadFile(String fileName, ETaskBusinessType businessType, Long taskId) {
        File file = new File(fileName);
        String url = this.cosDependency.upload(genCosKey(businessType, fileName), file);
        FileUtils.delete(file);
        this.taskDependency.updateTaskStatus(taskId, ETaskStatus.HANDLE_SUCCESS, url);
    }

    public void exportFail(Long taskId) {
        this.taskDependency.updateTaskStatus(taskId, ETaskStatus.HANDLE_FAIL, (String) null);
    }

    private static String genCosKey(ETaskBusinessType businessType, String fileName) {
        StringJoiner joiner = new StringJoiner("/");
        return joiner.add("export").add(businessType.name().toLowerCase()).add(fileName).toString();
    }

}

